home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
090
/
pcalc.arc
/
PCALC.C
Wrap
Text File
|
1985-07-26
|
43KB
|
2,177 lines
/*
* This programmable integer arithmetic calculator features the following:
*
* + uses most operators found in the C language
* + 'if-else' and 'while-break' flow control constructs
* + "edit", "list", "load", "save", "run" and "exit" commands
* + built-in line oriented editor
* + built-in functions (expandable)
*
* The program is composed of 4 modules, the main keyboard command
* interpreter including all of the command handlers; the token parser;
* the statement parser; and the p-code program interpreter.
*
* COMMAND INTERPRETER
*
* The command interpreter prompts for a line of input from the console,
* hands it off to the parser and then to the p-code interpreter
* to be executed. The result of the STATEMENT is then printed using the
* current number radix (see BUILT-IN FUNCTION base(), below).
*
* EDITOR
*
* The line editor is similar in concept to the editor available in MS-BASIC.
* By ignoring TAB codes in program text, it was possible to keep the editor
* code extremely simple. Editor commands are:
*
* ^E ^X - display previous/next line in program buffer (up/down)
* ^S ^D - move cursor left/right
* ^W - "window" 22 lines of program buffer around current line
* ^C - enter character-insert mode, terminated with CR or LF
* ^V - enter line-insert mode, terminated with CR or LF
* ^B - delete character under cursor
* ^Y - delete current line
*
* Since the editor requires that a blank line always exist at the end of
* the buffer, there is no need for a line APPEND command.
*
* LANGUAGE SYNTAX
*
* CONSTANTS
*
* Constants may be either decimal, hexadecimal with a leading "0x" like
* in C or octal with a leading "0". Strings are delimited with a quote,
* but unlike C they may be terminated with a newline instead of a close
* quote. All the standard character escapes ('\n', '\r', etc.) may be
* used within strings.
*
* VARIABLES
*
* Only 52 global variables are available, these are referenced by a SINGLE
* lower or upper case letter (a-z and A-Z).
*
* OPERATORS
*
* Most standard C operators are available:
*
* + - / * % ! ~ & && | || ^ << >> < <= > >= == !=
* = , ( )
*
* The address operator is "@" instead of "&" and may be used only in front
* of a variable reference. All other operators behave as expected.
*
* EXPRESSIONS
*
* Parenthesized expressions are allowed. An expression may be terminated
* with either a newline or a semicolon.
*
* STATEMENTS
*
* Statements may be either an expression or a list of expressions delimited
* with "{" and "}" like in C. The "if-else" and "while-break" constructs
* behave as in C.
*
* BUILT-IN FUNCTIONS
*
* Since this program was designed to be expandable, it currently offers
* only a few built-in functions. These are:
*
* new() - erase the program buffer
* edit(n) - envoke the editor at line "n"
* list() - list the program buffer to CON:
* save(s) - save the program buffer in a file named "s"
* load(s) - load program buffer from file named "s"
* stop(n) - stop the program and print the integer "n"
* exit() - exit to CP/M
* base(n) - change output number base
* printf(..) - just like the standard printf() found in C
* nl() - output a newline to CON:
* putn(n) - output integer n in current output number base
* getn(v) - read an integer value from CON: to the address at v
* run(s) - chain to another program
* debug(n) - enable/disable calculator debug print statements
*
* OPERATION
*
* The program uses two buffers, one contains the program source lines
* (char *Prog[]) and the other a tokenized, RPN representation of the
* source (struct Opstk[]). The token parser and statement parser convert
* the source buffer into one-character tokens and stack them in Reverse
* Polish Notation (RPN) onto the Operator/Operand stack (Opstk). The
* p-code interpreter then scans through the stack and performs each
* operation in sequence. Results of operations are kept on a "value"
* stack (int Valstk[]). All built-in functions must maintain the integrity
* of this stack, since no stack frame exists to restore the stack pointer
* on exit from the function.
*/
#include <stdio.h>
#define DEBUG 1
/*
* Tokens
*/
#define T_EOL '.'
#define T_SEMICOLON ';'
#define T_EOF 'z'
#define T_POP 'p'
#define T_CONST 'C'
#define T_STRING 'S'
#define T_SYMBOL 'Y'
#define T_LBRACE '{'
#define T_RBRACE '}'
#define T_LPAREN '('
#define T_RPAREN ')'
#define T_COMMA ','
#define T_ASSIGN '='
#define T_POINT '$'
#define T_ADDR '@'
#define T_MUL '*'
#define T_DIV '/'
#define T_MOD '%'
#define T_ADD '+'
#define T_SUB '-'
#define T_NEG '_'
#define T_SHL 'L'
#define T_SHR 'R'
#define T_LT '<'
#define T_LE 'l'
#define T_GT '>'
#define T_GE 'g'
#define T_EQ 'q'
#define T_NE 'n'
#define T_NOT '~'
#define T_AND '&'
#define T_XOR '^'
#define T_IOR '|'
#define T_LNOT '!'
#define T_LAND 'a'
#define T_LIOR 'o'
#define T_FUNC 'F'
#define T_IF 'i'
#define T_ELSE 'e'
#define T_WHILE 'w'
#define T_BREAK 'b'
/*
* Program line buffer
*/
#define MAXLINES 128 /* max length of a program */
char *Prog[ MAXLINES ];
int Progptr, Progtop; /* program current line and last line pointer */
char Source; /* set when parsing from Prog[] buffer */
/*
* Default program file name
*/
char Filenm[ 16 ];
/*
* Operator/Operand buffer - contains tokenized version of source lines.
*/
#define MAXOPS 1024
struct {
char o_token;
int o_value;
} Opstk[ MAXOPS ];
int Opptr; /* current p-code pointer */
int Opsp; /* size of buffer */
/*
* Value (working) stack
*/
#define MAXVALS 128
int Valstk[ MAXVALS ];
int Valsp; /* top of stack ptr */
/* macro returns value on top of stack: */
#define TOS (Valstk[Valsp-1])
/*
* Built-in Functions and jump table
*/
#define MAXFUNCS 14
int f_printf(), f_base(), f_nl(), f_putn(), f_getn(), f_run(), f_debug(),
f_new(), f_edit(), f_list(), f_save(), f_load(), exit(), f_stop();
struct _functab {
char *f_name;
int (*f_addr)();
} Functab[ MAXFUNCS ];
/*
* Keyword lookup table
*/
#define MAXKEYS 4
struct _keytab {
char *k_name;
char k_value;
} Keytab[ MAXKEYS ];
/*
* Symbol Table - symbols are referenced by a single letter (a-z or A-Z)
*/
int Symbols[ 52 ];
/*
* String table
*/
#define MAXSTRINGS 1024
char *Strings;
int Nextstr;
/*
* "if" and "while" stacks
*/
#define MAXIFS 10
#define MAXWHILES 10
int Ifstk[ MAXIFS ], Whstk[ MAXWHILES ];
char Ifsp, Whsp; /* top of stack ptrs */
/*
* Miscellaneous
*/
int Level; /* current lexical level */
int Parens; /* # of open parens (for error checking) */
int Commas; /* # of commas encountered in statement (argument count-1) */
char Token; /* current input token */
int Value; /* and its value */
#ifdef DEBUG
char Debug; /* interpreter debug flag */
#endif
char Eol; /* set when end of line encountered */
char Line[80]; /* input line, when not parsing from Prog[] buffer */
char *Lineptr; /* points to next character in either Line[] or Prog[] */
char *Ofmt; /* current output format (set by "base" command) */
int Error; /* set if on error */
char *skipws();
/*************************************************************
* MAIN PROGRAM *
*************************************************************/
main()
{
initialize();
for ( ;; )
{
reset();
prompt();
if ( gets( Line ) )
{
/*
* input line was not "run" - assume it's
* a valid statement. Attempt to parse the
* input line, generate pseudo-code and
* evaluate it.
*/
Source = 0;
getoken();
do
statement();
while ( !(Eol || Error) );
if ( !Error )
{
evaluate();
putresult( pop() );
}
}
}
}
/*************************************************************
* LEXICAL ANALYZER *
*************************************************************/
getoken()
{
/*
* Lexical Analyzer. Gets next token from the input line
* pointed to by "Lineptr" and advances "Lineptr" to next
* character. If end of input line is encountered, the
* "Eol" flag is set.
*/
char *cp, buf[ 128 ];
int i;
if ( Error )
goto done;
if ( Eol )
{
/*
* Found end of line, time to get a new line.
*/
Eol = 0;
if ( Source )
{
/*
* We're executing a program. Get next line of
* input from program buffer.
*/
if ( Progptr == Progtop )
/*
* End of program buffer.
*/
goto done;
else
Lineptr = Prog[ Progptr++ ];
}
else
{
/*
* Immediate mode. Check if lexical end of
* statement was not yet found.
*/
if ( Level )
{
prompt();
gets( Line );
}
Lineptr = Line;
}
#ifdef DEBUG
if ( Debug )
printf( "$%3d: %s\n", Progptr, Lineptr );
#endif
}
/*
* skip white space
*/
Lineptr = skipws( Lineptr );
if ( ! *Lineptr )
{
Eol = 1;
Token = T_EOL;
}
else if ( *Lineptr == '0' )
{
/*
* Check if it's a hex or octal constant
*/
Token = T_CONST;
++Lineptr;
if ( toupper( *Lineptr ) == 'X' )
{
++Lineptr;
for ( cp = buf; ishexdigit( *Lineptr ); )
*cp++ = *Lineptr++;
*cp = 0;
sscanf( buf, "%x", &Value );
}
else if ( isdigit( *Lineptr ) )
{
for ( cp = buf; isoctdigit( *Lineptr ); )
*cp++ = *Lineptr++;
*cp = 0;
sscanf( buf, "%o", &Value );
}
else
Value = 0;
}
else if ( *Lineptr == '"' )
{
/*
* It's a string constant. String constants are terminated
* by either the second quote encountered, or end of line.
* Value becomes the address of the string.
*/
++Lineptr;
for ( cp = buf; *Lineptr && *Lineptr != '"'; )
charescape( &cp );
if ( *Lineptr )
++Lineptr;
Value = *cp = 0;
Token = T_STRING;
/*
* Check if string is duplicated somewhere in string table.
*/
for ( cp=Strings; cp<Strings+Nextstr; cp += strlen(cp)+1 )
{
if ( ! strcmp( cp, buf ) )
{
Value = cp;
break;
}
}
if ( ! Value )
{
/*
* String is unique - make a new entry in string
* string table.
*/
if ( (i = Nextstr + strlen( buf ) + 1) > MAXSTRINGS )
err( "string space overflow" );
else
{
Value = &Strings[ Nextstr ];
strcpy( Value, buf );
Nextstr = i;
}
}
}
else if ( isdigit( *Lineptr ) )
{
/*
* It's a numeric constant, "Value" will be its value.
*/
Token = T_CONST;
for ( cp = buf; isdigit( *Lineptr ); )
*cp++ = *Lineptr++;
*cp = 0;
Value = atoi( buf );
}
else if ( Value = isfunc() )
{
/*
* It's a built-in function, "Value" will be the index
* into the function jump table.
*/
Token = T_FUNC;
--Value;
}
else if ( Token = iskeyword() )
;
else if ( Token = isoperator() )
/*
* It's a binary operator
*/
;
else if ( isalpha( *Lineptr ) )
{
/*
* It's a variable reference
*/
Token = T_SYMBOL;
if ( 'A'<=*Lineptr && *Lineptr<='Z' )
Value = *Lineptr - 'A';
else
Value = (toupper( *Lineptr ) - 'A') + 26;
++Lineptr;
}
else
{
/*
* Bad character in input line
*/
err( "syntax error" );
done:
Eol = 1; /* make immediate mode commands give up */
Source = 0; /* make run() give up */
Token = T_EOF; /* make statement() give up */
}
return Token;
}
char *
skipws( cp )
char *cp;
{
while ( *cp==' ' || *cp=='\t' )
++cp;
return cp;
}
charescape( cpp )
char **cpp;
{
/*
* Copy the next character from Lineptr into the string
* pointed to by "cpp". If a '\' is found, translate the
* following character(s) a la C.
*/
char *cp, c;
int i;
cp = *cpp;
if ( (c = *Lineptr++) == '\\' )
{
switch ( c = *Lineptr++ )
{
case 'b': *cp++ = '\b'; break;
case 'n': *cp++ = '\n'; break;
case 't': *cp++ = '\t'; break;
case 'f': *cp++ = '\f'; break;
case 'r': *cp++ = '\r'; break;
case '0':
case '1':
sscanf( Lineptr-1, "%o", &i );
Lineptr += 2;
*cp++ = i;
break;
default: *cp++ = c;
}
}
else
*cp++ = c;
*cpp = cp;
}
isfunc()
{
/*
* Check if string pointed to by "Lineptr" is the name of a
* built-function, return the function jump table index+1 if
* so and bump "Lineptr" to next character.
* Return 0 if not a function.
*/
char *cp, *bp, buf[ 80 ];
int funcno, i;
/*
* copy the name from input line buffer to a local buffer so
* we can use it to make a proper comparison to function names.
*/
for ( cp=Lineptr, bp=buf; isalpha( *cp ); )
*bp++ = *cp++;
*bp = 0;
/*
* compare it to all of the function names we know about.
*/
for ( funcno = i = 0; i < MAXFUNCS; ++i )
{
if ( ! strcmp( buf, Functab[ i ].f_name ) )
{
funcno = i + 1;
Lineptr = cp;
break;
}
}
return funcno;
}
iskeyword()
{
/*
* Check if string pointed to by "Lineptr" is a keyword.
* Return the keyword's token value and and bump "Lineptr"
* to next character, or 0 if not a keyword.
*/
char *cp, *bp, buf[ 80 ];
char keyno;
int i;
/*
* copy the name from input line buffer to a local buffer so
* we can use it to make a proper comparison to keywords.
*/
for ( cp=Lineptr, bp=buf; isalpha( *cp ); )
*bp++ = *cp++;
*bp = 0;
/*
* compare it to all of the keywords.
*/
for ( keyno = i = 0; i < MAXKEYS; ++i )
{
if ( ! strcmp( buf, Keytab[ i ].k_name ) )
{
keyno = Keytab[ i ].k_value;
Lineptr = cp;
break;
}
}
return keyno;
}
isoperator()
{
/*
* Check if string pointed to by "Lineptr" is an operator,
* return its token value and bump "Lineptr" to next character.
*/
int tkn;
char c;
switch ( *Lineptr )
{
case ',':
++Commas;
tkn = T_COMMA;
break;
case '=':
if ( Lineptr[1] == '=' )
{
tkn = T_EQ;
++Lineptr;
}
else
tkn = T_ASSIGN;
break;
case '!':
if ( Lineptr[1] == '=' )
{
tkn = T_NE;
++Lineptr;
}
else
tkn = T_LNOT;
break;
case '<':
if ( (c = Lineptr[1]) == '<' )
{
tkn = T_SHL;
++Lineptr;
}
else if ( c == '=' )
{
tkn = T_LE;
++Lineptr;
}
else
tkn = T_LT;
break;
case '>':
if ( (c = Lineptr[1]) == '>' )
{
tkn = T_SHR;
++Lineptr;
}
else if ( c == '=' )
{
tkn = T_GE;
++Lineptr;
}
else
tkn = T_GT;
break;
case '(':
++Parens;
tkn = T_LPAREN;
break;
case ')':
--Parens;
tkn = T_RPAREN;
break;
case '&':
if ( Lineptr[1] == '&' )
{
tkn = T_LAND;
++Lineptr;
}
else
tkn = T_AND;
break;
case '|':
if ( Lineptr[1] == '|' )
{
tkn = T_LIOR;
++Lineptr;
}
else
tkn = T_IOR;
break;
default:
if ( instr( *Lineptr, ";@{}*/%+-^~" ) )
tkn = *Lineptr;
else
tkn = 0;
}
if ( tkn )
++Lineptr;
return tkn;
}
skipnl()
{
while ( Token==T_EOL )
getoken();
}
/*************************************************************
* STATEMENT PARSER *
**************************************************************/
statement()
{
/*
* Parse a statement. The BNF for statements is:
* <statement> := <expression> <eol> |
* '{' <statement-list> '}'
* and, of course:
* <statement-list> := <eol> |
* <statement> <eol> |
* <statement-list> <statement> <eol>
* finally:
* <eol> := '\n' |
* ';' |
* ';' '\n'
*/
start:;
switch ( Token )
{
case T_EOL:
getoken();
goto start;
case T_SEMICOLON:
getoken();
skipnl();
case T_EOF:
break;
case T_IF:
++Level;
doif();
if ( Token!=T_EOF )
--Level;
break;
case T_ELSE:
doelse();
break;
case T_WHILE:
++Level;
dowhile();
if ( Token!=T_EOF )
--Level;
break;
case T_BREAK:
dobreak();
break;
case T_LBRACE:
++Level;
getoken();
do
statement();
while ( !Error && Token != T_RBRACE && Token!=T_EOF );
if ( Token!=T_EOF )
{
getoken();
--Level;
}
break;
case T_RBRACE:
if ( !Level )
err( "'{' missing" );
break;
case T_RPAREN:
if ( Parens<0 )
err( "'(' missing" );
break;
default:
expression();
generate( T_POP, 0 );
}
if ( Token == T_EOF && Level )
err( "incomplete statement" );
}
doif()
{
/*
* Parse an "if" statement:
* 'if' <expression> <statement>
*/
getoken();
expression();
/*
* Save current operator stack pointer for backpatching later.
* This is pushed onto a stack so that it will be available for
* possible future "else" statements.
*/
pushif( Opsp );
/*
* generate a "jump if value on stack is zero" code.
*/
generate( T_IF, -1 );
/*
* parse the <statement> part, then backpatch the above
* "jump if zero" opcode to point to next program line.
*/
statement();
skipnl();
if ( Token == T_ELSE )
doelse();
Opstk[ popif() ].o_value = Opsp;
}
doelse()
{
/*
* Parse an "else" statement.
* 'if' <expression> <statement> 'else' <statement>
*/
int p;
/*
* generate a "jump to end of if-else" opcode, then backpatch
* the "jump if zero" opcode generated by doif() to point to
* here.
*/
getoken();
p = popif();
pushif( Opsp );
generate( T_WHILE, -1 );
Opstk[ p ].o_value = Opsp;
statement();
}
dowhile()
{
/*
* Parse a "while" statement.
* 'while' <expression> <statement>
*/
int p;
/*
* Save program counter of <expression> part for
* "jump to top of loop" code to be generated later.
*/
p = Opsp;
getoken();
expression();
/*
* Save operator stack pointer of "jump if top of stack is zero"
* code (break out of loop code). This is pushed onto a stack
* so that it will be available for future "break" statements.
*/
pushwhile( Opsp );
generate( T_IF, -1 );
/*
* Parse the <statement> part, then generate code to jump back to
* top of loop.
*/
statement();
generate( T_WHILE, p );
/*
* Backpatch "jump if zero" opcode generated above.
*/
Opstk[ popwhile() ].o_value = Opsp;
}
dobreak()
{
/*
* Parse a "break" statement. Generate code to push a zero onto
* stack, then jump to the loop end test at top of loop. This test
* will find a zero on the stack and jump to the end of the loop.
*/
getoken();
generate( T_CONST, 0 );
generate( T_WHILE, pushwhile( popwhile() ) );
}
expression()
{
/*
* Parse an expression. Expressions have the following syntax:
* <expression> := <primary> <operator> <primary>
* so the first thing to look for is a primary.
*/
int lvalue;
char notempty;
/*
* Check if end of expression first
*/
if ( endofexpr() )
return 0;
else
{
notempty = 1; /* assume not the empty expression: "()" */
if ( !(lvalue = primary()) )
err( "bad expression" );
else if ( lvalue == 2 )
notempty = 0; /* it was the expression "()" */
else if ( endofexpr() )
{
/*
* The <primary> was an lvalue (variable reference)
* and the stack will contain its address. Generate
* code to load an integer from that address.
*/
if ( lvalue < 0 )
generate( T_POINT, 0 );
}
else
op_prim( 0, lvalue );
}
/*
* Return TRUE if it's an empty expression
*/
return notempty;
}
endofexpr()
{
/*
* Return TRUE if current Token marks end of an expression
*/
return Eol || Error ||
Token==T_RPAREN || Token==T_LBRACE ||
Token==T_RBRACE || Token==T_SEMICOLON;
}
op_prim( precedence, lvalue )
int precedence; /* precedence of current <operator> */
int lvalue; /* type of current <primary>: -1 => lvalue */
/* 0 => no <primary> (error) */
/* 1 => rvalue */
{
/*
* Parse the <operator> <primary> part of an expression.
* "precedence" is the PREVIOUS <operator>'s precedence level
* (0=low, +n=high).
*/
char tkn;
int pr, lv;
/*
* Loop until end of <expression> is found
*/
while ( ! endofexpr() )
{
/*
* Get the precedence level of current <operator> ("pr").
* If it is greater than previous operator ("precedence"),
* get the next <primary> and do another <operator> <primary>
* NOTE: For left-to-right associativity, the condition
* pr > precedence
* must be true. for right-to-left associativity,
* pr >= precedence
* must be true (assignment operator only).
*/
if ( !(pr = binop( Token )) )
{
/*
* Found two (possibly) consecutive primaries.
*/
err( "missing operator" );
break;
}
if (
(pr>precedence && pr>0) ||
(Token==T_ASSIGN && pr>=precedence)
)
{
if ( Token == T_ASSIGN )
{
if ( lvalue > 0 )
err( "= needs and lvalue" );
}
else if ( lvalue < 0 )
generate( T_POINT, 0 );
/*
* Save the operator token and do a primary.
*/
tkn = Token;
getoken();
if ( ! (lv = primary()) )
err( "missing operand" );
/*
* Now look at the next operator. If its precedence
* is greater than this one ("tkn" above), generate
* code for it BEFORE this one.
*/
lvalue = op_prim( pr, lv );
if ( Token != T_ASSIGN && lvalue < 0 )
{
/*
* Next operator is not the assignment op.
* and the current <primary> is an lvalue,
* therefore generate a "load from address
* on top of stack" instruction.
*/
generate( T_POINT, 0 );
/*
* This makes it an rvalue now.
*/
lvalue = 1;
}
else if ( tkn!=T_ASSIGN && Token==T_ASSIGN )
{
/*
* YEECH! this is the only way I know of to
* detect errors like: a+b=c
*/
err( "= needs an lvalue" );
}
/*
* Generate the instruction for the current operator.
*/
if ( tkn!=T_COMMA )
generate( tkn, 0 );
}
else
break;
}
return lvalue;
}
primary()
{
/*
* Parse a primary. Primaries have the following syntax:
* <primary> := <constant> |
* '(' <expression> ')' |
* <unary op> <primary> |
* <function> <primary>
*/
int rtn, val, savcommas, needparen;
/*
* Return value:
* -1 => the <primary> is an lvalue
* 0 => not a <primary> (usually end of expr or syntax error)
* 1 => the <primary> is an rvalue
* 2 => the <primary> is the empty expression "()"
*/
rtn = 1;
switch ( Token )
{
case T_ADDR: /* address operator */
getoken();
if ( Token != T_SYMBOL )
err( "@ not followed by a variable" );
else
{
Token = T_CONST;
Value = &Symbols[ Value ];
}
goto const;
case T_SYMBOL: /* a symbol */
rtn = -1;
case T_CONST: /* a constant */
case T_STRING: /* a string constant */
;
const:
generate( Token, Value );
getoken();
break;
case T_LPAREN: /* a parenthesized expression */
if ( getoken() == T_RPAREN )
rtn = 2; /* special empty expression: () */
else
expression();
if ( Token != T_RPAREN )
{
err( "missing ')'" );
rtn = 0;
}
else
getoken();
break;
case T_SUB: /* unary - */
/*
* The lexical analyzer is not smart enough to recognize
* unary operators (+ and -), that's why we have to do
* it here
*/
getoken();
expression();
generate( T_NEG, 0 );
break;
case T_NOT: /* unary ~ */
getoken();
expression();
generate( T_NOT, 0 );
break;
case T_ADD: /* unary + */
getoken();
expression();
break;
case T_LNOT: /* unary ! */
getoken();
expression();
generate( T_LNOT, 0 );
break;
case T_FUNC: /* built-in function */
val = Value;
/*
* Keep track of number of arguments pushed onto stack...
*/
savcommas = Commas;
Commas = needparen = 0;
if ( getoken() == T_LPAREN )
{
getoken();
needparen = 1;
}
if ( !expression() )
--Commas; /* found the empty expression "()" */
if ( needparen )
{
if ( Token!=T_RPAREN )
err( "missing ')'" );
getoken();
}
/*
* set # of arguments
*/
generate( T_COMMA, Commas+1 );
generate( T_FUNC, val );
Commas = savcommas;
break;
default:
/*
* Not a primary
*/
rtn = 0;
}
return rtn;
}
binop( op )
char op;
{
/*
* Determine if "op" is a binary operator and return its
* precedence level if so. If not, return 0.
*/
switch ( op )
{
case T_COMMA:
return 1;
case T_ASSIGN:
return 2;
case T_IOR:
return 3;
case T_XOR:
return 4;
case T_AND:
return 5;
case T_LT:
case T_GT:
case T_LE:
case T_GE:
case T_EQ:
case T_NE:
return 6;
case T_LAND:
case T_LIOR:
return 7;
case T_SHL:
case T_SHR:
return 8;
case T_ADD:
case T_SUB:
return 9;
case T_MUL:
case T_DIV:
case T_MOD:
return 10;
case T_NOT:
case T_LNOT:
return 11;
}
return 0;
}
generate( tkn, val )
char tkn;
{
/*
* Push the given token and value onto the Operator/Operand stack.
*/
if ( Opsp < MAXOPS )
{
Opstk[ Opsp ].o_token = tkn;
Opstk[ Opsp ].o_value = val;
#ifdef DEBUG
if ( Debug )
printf( "+%3d: %c %d\n", Opsp, tkn, val );
#endif
++Opsp;
}
else
err( "program too long" );
}
pushif( n )
{
if ( Ifsp < MAXIFS )
Ifstk[ Ifsp++ ] = n;
else
err( "too many nested 'if's" );
return n;
}
popif()
{
if ( Ifsp )
return Ifstk[ --Ifsp ];
err( "mismatched 'else'" );
}
pushwhile( n )
{
if ( Whsp < MAXWHILES )
Whstk[ Whsp++ ] = n;
else
err( "too many nested 'while's" );
return n;
}
popwhile()
{
if ( Whsp )
return Whstk[ --Whsp ];
err( "'break' not inside a 'while'" );
}
/*************************************************************
* EXPRESSION EVALUATOR *
**************************************************************/
/*
* NOTE: The comments make reference to "lvalues" and "rvalues". These
* are attributes of <primaries> (primaries, for the layman, are things
* like constants and variables, and parenthesized expressions. If you
* don't know what an expression is, you shouldn't be a reading this!).
* If a <primary> is an "lvalue", it means that it can usually be found on
* LEFT-HAND side of an assignment operator. "rvalues" can only be found
* on the RIGHT-HAND side of an assignment. Simply stated, only things like
* variables can be used as both "lvalues" and "rvalues", whereas things
* like constants and parenthesized expressions can only be "rvalues" since
* it wouldn't make sense to say: 12 = 5.
*/
evaluate()
{
/*
* Evaluate an expression by popping operators and operands
* from the Operator/Operand stack and performing each indicated
* operation.
*/
int val, *ip, i;
char op;
for ( Opptr=0; Opptr<Opsp; ++Opptr )
{
op = Opstk[ Opptr ].o_token;
val = Opstk[ Opptr ].o_value;
/*
* Stop program if ^C is entered.
*/
if ( bios( 2, 0 ) && getkey()==3 )
break;
#ifdef DEBUG
if ( Debug )
{
printf( "-%3d: %c %d:", Opptr, op, val );
for ( i=0; i<Valsp; ++i )
printf( " %d", Valstk[ i ] );
newline();
}
#endif
switch ( op )
{
case T_CONST:
case T_STRING:
push( val );
break;
case T_SYMBOL:
/*
* Push the address of a variable
*/
push( &Symbols[ val ] );
break;
case T_POINT:
/*
* Fetch an integer from address on top of stack.
* This usually follows a T_SYMBOL when the symbol
* is not being used as an "lvalue".
*/
ip = pop();
push( *ip );
break;
case T_IF:
/*
* Jump to the program line # given by operand
* if top of stack is zero.
*/
if ( !pop() )
Opptr = val - 1;
break;
case T_WHILE:
/*
* Jump to the program line # given by operand
*/
Opptr = val - 1;
break;
case T_POP:
/*
* Pop the stack. Usually follows an <expression>
*/
pop();
break;
case T_COMMA:
/*
* Set # of arguments on stack
*/
Commas = val;
break;
case T_FUNC:
/*
* Execute a built-in function
*/
(*Functab[ Opstk[ Opptr ].o_value ].f_addr)();
break;
case T_ASSIGN:
/*
* Assignment operator: The item on top of stack is
* the "rvalue", second on stack is the "lvalue"
* (an address where to store the "rvalue"). The
* "rvalue" gets pushed back on top of the stack.
*/
val = pop();
ip = pop();
push( *ip = val );
break;
case T_NOT:
TOS = ~TOS;
break;
case T_LNOT:
TOS = !TOS;
break;
case T_NEG:
TOS = -TOS;
break;
default:
/*
* All others are binary operators.
*/
val = pop();
switch ( op )
{
case T_ADD:
TOS += val;
break;
case T_SUB:
TOS -= val;
break;
case T_MUL:
TOS *= val;
break;
case T_DIV:
TOS /= val;
break;
case T_MOD:
TOS %= val;
break;
case T_LT:
TOS = TOS < val;
break;
case T_GT:
TOS = TOS > val;
break;
case T_LE:
TOS = TOS <= val;
break;
case T_GE:
TOS = TOS >= val;
break;
case T_EQ:
TOS = TOS == val;
break;
case T_NE:
TOS = TOS != val;
break;
case T_SHL:
TOS = TOS << val;
break;
case T_SHR:
TOS = TOS >> val;
break;
case T_AND:
TOS &= val;
break;
case T_XOR:
TOS ^= val;
break;
case T_IOR:
TOS |= val;
break;
case T_LAND:
TOS = TOS && val;
break;
case T_LIOR:
TOS = TOS || val;
break;
default:
err( "parser error" );
}
}
}
}
push( val )
{
if ( Valsp >= MAXVALS )
err( "stack overflow" );
return Valstk[ Valsp++ ] = val;
}
pop()
{
if ( --Valsp < 0 )
Valsp = 0;
return Valstk[ Valsp ];
}
/*************************************************************
* BUILT-IN FUNCTIONS *
**************************************************************/
/*
* NOTE: All functions expect the correct number of arguments on the
* stack. These arguments are removed and exactly one argument is left in
* their place. Thus, a built-in function is a transform that results in
* a single rvalue.
*/
f_printf()
{
/*
* usage: printf( a0, a1, ... a9 )
* does: do a formatted print, a la printf()
* stacks: # of arguments printed
*/
int a[ 10 ];
getargs( 10, a );
push( printf(a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]) );
}
f_base()
{
/*
* usage: base( n )
* does: sets output number base
* stacks: the argument n
*/
int n;
getargs( 1, &n );
switch ( n )
{
case 8:
Ofmt = "0%o";
break;
case 16:
Ofmt = "0x%x";
break;
case 10:
default:
Ofmt = "%d";
break;
}
push( n );
}
f_run()
{
/*
* usage: run( s )
* does: chain to program in filename pointed to by "s". If "s"
* not given, executes source already in program buffer.
* stacks: 1 if successful, 0 otherwise
*/
char *s;
if ( getargs( 1, &s ) )
{
Commas = 1;
push( s );
if ( !f_load() )
{
push( 0 );
return;
}
}
reset();
*Line = 0;
Source = 1;
getoken();
while ( Source )
statement();
/*
* This function was called from evaluate(), so
* remember to back up p-code pointer by one.
*/
--Opptr;
}
f_nl()
{
/*
* usage: nl()
* does: outputs a newline to CON:
* stacks: a newline character (0x0a)
*/
getargs( 0, 0 );
newline();
push( '\n' );
}
f_putn()
{
/*
* usage: putn( n )
* does: prints numeric constant in the current number base
* stacks: the number
*/
int n;
getargs( 1, &n );
printf( Ofmt, n );
push( n );
}
f_getn()
{
/*
* usage: getn( v )
* does: reads a number into the address at "v" (assumed to be
* a variable). If "v" is not given, leaves number on stack.
* stacks: number read
*/
int *ip, n;
char buf[ 128 ];
gets( buf );
n = atoi( buf );
if ( getargs( 1, &ip ) )
push( *ip = n );
else
push( n );
}
f_debug()
{
/*
* usage: debug( v )
* does: sets/resets the interpreter's debug flag, depending on v
* stacks: v
*/
int v;
getargs( 1, &v );
#ifdef DEBUG
Debug = v;
#endif
push( v );
}
f_new()
{
/*
* Erase the entire program buffer by freeing up all memory
*/
getargs( 0, 0 );
new();
push( 0 );
}
new()
{
for ( Progptr=0; Progptr<Progtop; ++Progptr )
free( Prog[ Progptr ] );
Progptr = Progtop = 0;
}
f_load()
{
char *file, iobuf[ BUFSIZ ], rtn;
if ( !getargs( 1, &file ) )
file = Filenm;
if ( *file && fopen( file, iobuf ) != -1 )
{
rtn = 1;
new();
while ( fgets( Line, iobuf ) )
{
Line[ strlen( Line ) - 1 ] = 0;
if ( !makline( Progtop++, Line ) )
{
puts( "file too big\n" );
rtn = 0;
break;
}
}
fclose( iobuf );
}
else
{
puts( "file not found\n" );
rtn = 0;
}
if ( rtn )
strcpy( Filenm, file );
push( rtn );
return rtn;
}
f_save()
{
char *file, iobuf[ BUFSIZ ], rtn;
int i;
if ( !getargs( 1, &file ) )
file = Filenm;
if ( *file && fcreat( file, iobuf ) != -1 )
{
for ( i=0; i<Progtop; ++i )
{
fputs( Prog[ i ], iobuf );
putc( '\n', iobuf );
}
putc( 26, iobuf );
fclose( iobuf );
rtn = 1;
}
else
{
puts( "file not created\n" );
rtn = 0;
}
if ( rtn )
strcpy( Filenm, file );
push( rtn );
return rtn;
}
f_edit()
{
/*
* Program buffer editor.
*/
char *cp, col, lastcol;
int i, c;
if ( getargs( 1, &i ) )
Progptr = i - 1;
push( i );
/*
* Initialize: do some bounds checking on current program line ptr,
* and redraw the current line.
*/
;start:
col = 0;
if ( !Progtop )
{
/*
* There's always one blank line at the end of the buffer.
* Therefore, we only need a line INSERT command, never an
* APPEND...
*/
Progptr = 0;
addline( "" );
}
else if ( Progptr && Progptr >= Progtop )
Progptr = Progtop - 1;
else if ( Progptr < 0 )
Progptr = 0;
redraw:
newline();
fmtlno( Progptr );
puts( cp = Prog[ Progptr ] );
lastcol = strlen( cp );
if ( col > lastcol )
col = lastcol;
fmtlno( Progptr );
for ( i=0; i<col; ++i )
putchar( cp[i] );
/*
* Command loop
*/
for ( ;; )
{
switch ( c = getkey() )
{
case '\r': /* exit */
case '\n':
goto done;
case 5: /* up */
if ( Progptr )
{
--Progptr;
goto start;
}
break;
case 24: /* down */
if ( Progptr < Progtop-1 )
{
++Progptr;
goto start;
}
break;
case 19: /* left */
case 8:
if ( col )
{
putchar( '\b' );
--col;
}
break;
case 4: /* right */
if ( col < lastcol )
putchar( cp[ col++ ] );
break;
case 23: /* redraw window */
newline();
newline();
if ( (i=Progptr-11) < 0 )
i = 0;
for ( c=i; c<i+22 && c<Progtop; ++c )
fmtline( c );
goto redraw;
case 22: /* insert line mode */
newline();
newline();
for ( ;; )
{
fmtlno( Progptr );
if ( !gets( Line ) )
break;
if ( !insline( Progptr++, Line ) )
break;
}
goto start;
case 3: /* insert character mode */
if ( Progptr < Progtop-1 )
{
for ( i=0; i<col; ++i )
Line[i] = cp[i];
gets( &Line[i] );
strcat( Line, &cp[i] );
free( cp );
makline( Progptr, Line );
goto redraw;
}
break;
case 25: /* delete line */
if ( Progptr < Progtop-1 )
{
delline( Progptr );
goto start;
}
break;
case 2: /* delete character */
for ( i=col; i<lastcol; ++i )
cp[i] = cp[i+1];
goto redraw;
default:
if ( ' '<=c && c<='~' && col < lastcol )
putchar( cp[ col++ ] = c );
break;
}
}
done:
newline();
}
f_list()
{
int n[2], i;
n[0] = 1; n[1] = Progtop;
getargs( 2, n );
puts( Filenm );
newline();
for ( i=n[0]-1; i<n[1]; ++i )
fmtline( i );
push( 0 );
}
f_stop()
{
int n;
getargs( 1, &n );
Opptr = Opsp;
push( n );
}
getargs( n, ip )
int *ip;
{
/*
* Remove items from the Valstk and adjust stackptr.
*/
int argc;
if ( Commas > n )
{
/*
* More arguments on stack than expected - remove excess
*/
while ( Commas-- > n )
pop();
}
else if ( Commas < n )
{
/*
* Less arguments than expected - reduce n
*/
n = Commas;
}
argc = 0;
while ( n-- )
{
++argc;
ip[ n ] = pop();
}
return argc;
}
/*************************************************************
* PROGRAM BUFFER MANIPULATION ROUTINES *
*************************************************************/
makline( lno, line )
char *line;
{
/*
* Copy the string at "line" into the program buffer at "lno".
* A block of memory will be allocated for the new string.
*/
char *cp;
if ( cp = Prog[ lno ] = malloc(strlen(line) + 1) )
{
strcpy( cp, line );
return 1;
}
return 0;
}
addline( line )
char *line;
{
/*
* Add the string at "line" to the end of the program buffer.
*/
if ( Progtop >= MAXLINES )
return 0;
if ( makline( Progtop, line ) )
{
++Progtop;
return 1;
}
return 0;
}
insline( lno, line )
char *line;
{
/*
* Insert the string, "line" before "lno" in the program buffer.
*/
int i;
if ( lno >= Progtop )
return 0;
if ( Progtop )
{
/*
* There is at least one line in the buffer. First append
* a new line to the end of the program buffer and duplicate
* the last line.
*/
i = Progtop;
if ( i < MAXLINES )
{
++Progtop;
/*
* Move all lines below "lno" down
*/
while ( i-- > lno )
Prog[ i+1 ] = Prog[ i ];
/*
* Free up the string at "lno" and create a new
* line there.
*/
return makline( lno, line );
}
else
return 0;
}
else
/*
* Nothing in program buffer yet - append the new line.
*/
return addline( line );
return 1;
}
delline( lno )
{
char *cp;
int i;
if ( lno >= Progtop )
return 0;
/*
* There is at least one line in the buffer. First delete
* the line at "lno" in the program buffer.
*/
free( Prog[ lno ] );
/*
* Then move all lines below "lno" up.
*/
while ( ++lno < Progtop )
Prog[ lno-1 ] = Prog[ lno ];
--Progtop;
return 1;
}
fmtline( n )
{
fmtlno( n );
puts( Prog[ n ] );
newline();
}
fmtlno( n )
{
printf( "\r%4d:", n+1 );
}
/*************************************************************
* MISCELLANEOUS *
**************************************************************/
initialize()
{
/*
* Initialization routine - for compilers that do not support
* global variable initialization.
*/
/*
* initialize function table
*/
Functab[0].f_name = "printf";
Functab[0].f_addr = f_printf;
Functab[1].f_name = "base";
Functab[1].f_addr = f_base;
Functab[2].f_name = "run";
Functab[2].f_addr = f_run;
Functab[3].f_name = "nl";
Functab[3].f_addr = f_nl;
Functab[4].f_name = "putn";
Functab[4].f_addr = f_putn;
Functab[5].f_name = "getn";
Functab[5].f_addr = f_getn;
Functab[6].f_name = "debug";
Functab[6].f_addr = f_debug;
Functab[7].f_name = "new";
Functab[7].f_addr = f_new;
Functab[8].f_name = "edit";
Functab[8].f_addr = f_edit;
Functab[9].f_name = "list";
Functab[9].f_addr = f_list;
Functab[10].f_name = "save";
Functab[10].f_addr = f_save;
Functab[11].f_name = "load";
Functab[11].f_addr = f_load;
Functab[12].f_name = "exit";
Functab[12].f_addr = exit;
Functab[13].f_name = "stop";
Functab[13].f_addr = f_stop;
/*
* keyword lookup table
*/
Keytab[0].k_name = "if";
Keytab[0].k_value = T_IF;
Keytab[1].k_name = "else";
Keytab[1].k_value = T_ELSE;
Keytab[2].k_name = "while";
Keytab[2].k_value = T_WHILE;
Keytab[3].k_name = "break";
Keytab[3].k_value = T_BREAK;
/*
* string table
*/
Strings = malloc( MAXSTRINGS );
/*
* display number radix
*/
push( 10 );
f_base();
pop();
}
reset()
{
/*
* Initialize parser variables
*/
Opptr=Opsp=Valsp=Ifsp=Whsp=Level=Parens=Commas=Error=Progptr = 0;
Eol = 1;
}
putresult( result )
{
/*
* Print results of an expression in current output format
*/
printf( Ofmt, result );
newline();
}
prompt()
{
int i;
for ( i=0; i<Level; ++i )
putchar( '\t' );
puts( "> " );
}
err( s )
{
/*
* Display an error message
*/
if ( ! Error )
{
/*
* We're only interested in the first one encountered
* on a line, since error recovery is non-existent.
*/
if ( Source )
fmtlno( Progptr );
puts( s );
newline();
Error = 1;
}
}
newline()
{
putchar( '\n' );
}
ishexdigit( c )
char c;
{
return instr( c, "0123456789abcdefABCDEF" );
}
isoctdigit( c )
char c;
{
return instr( c, "01234567" );
}
instr( c, s )
char c, *s;
{
/*
* Return TRUE if the character "c" is in the string "s"
*/
while ( *s )
if ( c == *s++ )
return 1;
return 0;
}
getkey()
{
/*
* Get a key directly from keyboard
*/
return bios( 3, 0 );
}
gets( s )
char *s;
{
int i, c;
i = 0;
while ( i<79 )
{
switch ( c = getkey() )
{
case '\r':
case '\n':
newline();
goto done;
case '\t':
for ( c=0; c<3 && i<79; ++c )
putchar( s[ i++ ] = ' ' );
break;
case '\b':
if ( i )
{
--i;
puts( "\b \b" );
}
break;
case 3:
exit();
case 4:
Debug = !Debug;
break;
default:
if ( ' '<=c && c<='~' )
putchar( s[ i++ ] = c );
}
}
done:
s[ i ] = 0;
return i;
}